﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Fusion.Async.Coroutines;
using Fusion.Utilities.Singleton;

namespace Fusion.Frameworks.Assets
{
    public class AssetBundleCreateAsynData
    {
        private AssetBundleCreateRequest assetBundleCreateRequest;
        private AssetData assetData;

        public AssetBundleCreateAsynData(AssetBundleCreateRequest assetBundleCreateRequest)
        {
            this.assetBundleCreateRequest = assetBundleCreateRequest;
            assetData = new AssetData();
        }

        public AssetBundleCreateRequest AssetBundleCreateRequest { get => assetBundleCreateRequest; }
        public AssetData AssetData { get => assetData; }

        public void Submit()
        {
            if (assetData.TempCount > 0)
            {
                AssetReferences.Instance.Reference(assetBundleCreateRequest.assetBundle, false, assetData.TempCount);
            }
            if (assetData.PersistentCount > 0)
            {
                AssetReferences.Instance.Reference(assetBundleCreateRequest.assetBundle, true, assetData.PersistentCount);
            }
        }
    }


    /// <summary>
    /// 提供AssetBundle及Asset的加载
    /// </summary>
    [DisallowMultipleComponent]
    public class AssetsManager : SingletonBehaviour<AssetsManager>
    {
        private static AssetBundleManifest assetBundleManifest;
        private static Dictionary<string, AssetBundleCreateAsynData> asyncCreateDatas = new Dictionary<string, AssetBundleCreateAsynData>();

        public static string StreamingLoadPath
        {
            get
            {
                return $"{Application.streamingAssetsPath}/ManagedAssets";
            }
        }

        public static string PersistentLoadPath
        {
            get
            {
                return $"{Application.persistentDataPath}/ManagedAssets";
            }
        }

        private static AssetBundle LoadFromFile(string assetBundleName)
        {
            string persistentPath = Path.Combine(PersistentLoadPath, assetBundleName);
            string loadPath = File.Exists(persistentPath) ? persistentPath : Path.Combine(StreamingLoadPath, assetBundleName);

            AssetBundle assetBundle = AssetBundle.LoadFromFile(loadPath);
            return assetBundle;
        }

        private static AssetBundleCreateRequest LoadFromFileAsync(string assetBundleName)
        {
            string persistentPath = Path.Combine(PersistentLoadPath, assetBundleName);
            string loadPath = File.Exists(persistentPath) ? persistentPath : Path.Combine(StreamingLoadPath, assetBundleName);
            AssetBundleCreateRequest assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(loadPath);
            return assetBundleCreateRequest;
        }

        public static AssetBundleManifest AssetBundleManifest { get => assetBundleManifest; }

        public AssetBundle LoadAssetBundle(string path, bool persistentAsset = false)
        {
            LoadAssetBundleDependencies(path, persistentAsset);
            string assetBundleName = AssetsConfig.GetAssetBundleName(path);

            AssetBundle assetBundle = AssetReferences.Instance.Get(assetBundleName);
            if (assetBundle == null)
            {
                AssetBundleCreateAsynData assetBundleCreateAsynData;
                if (asyncCreateDatas.TryGetValue(assetBundleName, out assetBundleCreateAsynData))
                {
                    assetBundleCreateAsynData.AssetData.Reference(persistentAsset);
                }
                else
                {
                    assetBundle = LoadFromFile(assetBundleName);
                    AssetReferences.Instance.Reference(assetBundle, persistentAsset);
                }
            }
            else
            {
                AssetReferences.Instance.Reference(assetBundle, persistentAsset);
            }

            return assetBundle;
        }
        public AssetBundle[] LoadAssetBundleDependencies(string path, bool persistentAsset = false)
        {
            string assetBundleName = AssetsConfig.GetAssetBundleName(path);
            string[] assetBundleDependencies = assetBundleManifest.GetAllDependencies(assetBundleName);
            int nameLength = assetBundleDependencies.Length;
            AssetBundle[] assetBundles = new AssetBundle[nameLength];
            for (int i = 0; i < nameLength; i++)
            {
                string dependencyName = assetBundleDependencies[i];
                AssetBundleCreateAsynData assetBundleCreateAsynData;
                if (asyncCreateDatas.TryGetValue(dependencyName, out assetBundleCreateAsynData))
                {
                    assetBundleCreateAsynData.AssetData.Reference(persistentAsset);
                }
                else
                {
                    assetBundles[i] = AssetReferences.Instance.Get(dependencyName);
                    if (assetBundles[i] == null)
                    {
                        assetBundles[i] = LoadFromFile(dependencyName);
                    }
                    AssetReferences.Instance.Reference(assetBundles[i], persistentAsset);
                }
            }
            return assetBundles;
        }

        private IEnumerator LoadAssetBundleAsyncHandle(string assetBundleName, bool persistentAsset = false, Action<AssetBundle> finishCallback = null)
        {
            AssetBundle assetBundle = AssetReferences.Instance.Get(assetBundleName);
            if (assetBundle == null)
            {
                AssetBundleCreateAsynData assetBundleCreateAsynData;

                if (asyncCreateDatas.TryGetValue(assetBundleName, out assetBundleCreateAsynData))
                {
                    assetBundleCreateAsynData.AssetData.Reference(persistentAsset);
                    while(!assetBundleCreateAsynData.AssetBundleCreateRequest.isDone)
                    {
                        yield return null;
                    }
                }
                else
                {
                    AssetBundleCreateRequest assetBundleCreateRequest = LoadFromFileAsync(assetBundleName);
                    assetBundleCreateAsynData = new AssetBundleCreateAsynData(assetBundleCreateRequest);
                    assetBundleCreateAsynData.AssetData.Reference(persistentAsset);
                    asyncCreateDatas[assetBundleName] = assetBundleCreateAsynData;
                    yield return assetBundleCreateRequest;
                    asyncCreateDatas.Remove(assetBundleName);
                    assetBundleCreateAsynData.Submit();
                }

                assetBundle = assetBundleCreateAsynData.AssetBundleCreateRequest.assetBundle;
            }
            else
            {
                AssetReferences.Instance.Reference(assetBundle, persistentAsset);
            }
            if (finishCallback != null)
            {
                finishCallback(assetBundle);
            }
        }

        public IEnumerator LoadAssetBundleAsync(string path, Action<AssetBundle> finishCallback = null, bool persistentAsset = false)
        {
            yield return LoadAssetBundleDependenciesAsync(path, null, persistentAsset);
            string assetBundleName = AssetsConfig.GetAssetBundleName(path);
            yield return LoadAssetBundleAsyncHandle(assetBundleName, persistentAsset, finishCallback);
        }

        public IEnumerator LoadAssetBundleDependenciesAsync(string path, Action<AssetBundle[]> finishCallback = null, bool persistentAsset = false)
        {
            string assetBundleName = AssetsConfig.GetAssetBundleName(path);
            string[] assetBundleDependencies = assetBundleManifest.GetAllDependencies(assetBundleName);
            int nameLength = assetBundleDependencies.Length;
            AssetBundle[] assetBundles = new AssetBundle[nameLength];
            for (int i = 0; i < nameLength; i++)
            {
                string dependencyName = assetBundleDependencies[i];
                int index = i;
                yield return LoadAssetBundleAsyncHandle(dependencyName, persistentAsset, delegate(AssetBundle assetBundle)
                {
                    assetBundles[index] = assetBundle;
                });
            }

            if (finishCallback != null)
            {
                finishCallback(assetBundles);
            }
        }

        public T Load<T>(string path, bool persistentAsset = false) where T : UnityEngine.Object
        {
#if FUSION_ASSETBUNDLE || !UNITY_EDITOR
            AssetBundle assetBundle = LoadAssetBundle(path, persistentAsset);
            if (assetBundle != null)
            {
                T asset = assetBundle.LoadAsset<T>(GetAssetNameByPath(path));
                return asset;
            }
            else
            {
                return null;
            }
#else
            T asset = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(GetPathWithSuffix($"Assets/GameAssets/{path}"));
            return asset;
#endif
        }

#if !FUSION_ASSETBUNDLE && UNITY_EDITOR
        private string GetPathWithSuffix(string path)
        {
            string name = GetAssetNameByPath(path);
            string dir = path.Substring(0, path.LastIndexOf("/"));
            string[] assets = UnityEditor.AssetDatabase.FindAssets(name, new string[] { dir });
            List<string> resultPaths = new List<string>();
            for (int index = 0; index < assets.Length; index++)
            {
                string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(assets[index]);
                int suffixStart = assetPath.LastIndexOf(".");
                if (suffixStart != -1)
                {
                    string pathWithoutSuffix = assetPath.Substring(0, assetPath.LastIndexOf("."));
                    if (pathWithoutSuffix == path)
                    {
                        resultPaths.Add(assetPath);
                    }
                }
            }

            if (resultPaths.Count == 1)
            {
                return resultPaths[0];
            }
            else if (resultPaths.Count > 1)
            {
                Debug.LogError($"There are assets with same name in directory [{dir}], please check and fix them.");
                return resultPaths[0];
            }
            else
            {
                Debug.LogError($"Can not find asset with name [{name}] in directory [{dir}].");
                return path;
            }

        }
#endif

        private IEnumerator LoadAsyncCoroutine<T>(string path, Action<T> finishCallback, bool persistentAsset = false) where T : UnityEngine.Object
        {
            AssetBundle assetBundle = null;
            yield return LoadAssetBundleAsync(path, delegate (AssetBundle assetBundleLoaded)
            {
                assetBundle = assetBundleLoaded;
            }, persistentAsset);

            AssetBundleRequest assetBundleRequest = assetBundle.LoadAssetAsync<T>(GetAssetNameByPath(path));
            yield return assetBundleRequest;
            if (finishCallback != null)
            {
                finishCallback((T)assetBundleRequest.asset);
            }
        }

        public void LoadAsync<T>(string path, Action<T> finishCallback = null, bool persistentAsset = false) where T : UnityEngine.Object
        {
#if FUSION_ASSETBUNDLE || !UNITY_EDITOR
            CoroutineManager.Instance.InvokeCoroutine(LoadAsyncCoroutine(path, finishCallback, persistentAsset));
#else
            T asset = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(GetPathWithSuffix($"Assets/GameAssets/{path}"));
            if (finishCallback != null)
            {
                finishCallback(asset);
            }
#endif
        }

        public string GetAssetNameByPath(string path)
        {
            return path.Substring(path.LastIndexOf('/') + 1);
        }

        public override void Initializate()
        {
            base.Initializate();
#if FUSION_ASSETBUNDLE || !UNITY_EDITOR
            AssetBundle assetBundle = LoadFromFile("ManagedAssets");
            assetBundleManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            assetBundle.Unload(false);
            Debug.Log("AssetBundle Enabled");
#endif
        }

        public void Release(string assetBundleName, int count = 1, bool persistentAsset = false)
        {
            if (assetBundleManifest != null)
            {
                AssetReferences.Instance.Release(assetBundleName, count, persistentAsset);

                string[] assetBundleDependencies = assetBundleManifest.GetAllDependencies(assetBundleName);
                for (int i = 0; i < assetBundleDependencies.Length; i++)
                {
                    AssetReferences.Instance.Release(assetBundleDependencies[i], count, persistentAsset);
                }
            }
        }

        public void ReleaseImmediate(string assetBundleName, int count = 1, bool persistentAsset = false)
        {
            if (assetBundleManifest != null)
            {
                AssetReferences.Instance.ReleaseImmediate(assetBundleName, count, persistentAsset);

                string[] assetBundleDependencies = assetBundleManifest.GetAllDependencies(assetBundleName);
                for (int i = 0; i < assetBundleDependencies.Length; i++)
                {
                    AssetReferences.Instance.ReleaseImmediate(assetBundleDependencies[i], count, persistentAsset);
                }
            }
        }
    }
}

